/**
 * 
 */
package ua.lviv.franko.solvers.oneDimention;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;

import ua.lviv.franko.FEMShapeFunction1D.FEMShapeFunction1D1;
import ua.lviv.franko.FEMShapeFunction1D.IFEM1D;
import ua.lviv.franko.integration.ConstantFunction1D;
import ua.lviv.franko.integration.GaussQuadrature;
import ua.lviv.franko.matrix.hash.lu.MatrixLUStyle;
import ua.lviv.franko.mlsFunctions.oneDimension.BaseFunctionCoupled;
import ua.lviv.franko.mlsFunctions.oneDimension.ICalculate;
import ua.lviv.franko.mlsFunctions.oneDimension.Particle;
import ua.lviv.franko.mlsFunctions.oneDimension.Values;
import ua.lviv.franko.mlsFunctions.oneDimension.ValuesHolder;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.CubicSpline;
import ua.lviv.franko.mlsFunctions.oneDimension.weightFunctions.IWeightFunction;
import ua.lviv.franko.solvers.ElementMatrix;
import ua.lviv.franko.solvers.IElement1D;

/**
 * @author marko.bekhta
 * 
 */
public class PreCalculatedEnrichedLinearElement implements IElement1D {
	public static final int							QuadraturePointsNumber	= 16;
	protected static double							RHO						= 2.5;
	private static final int						NUMBER_OF_NODES			= 5;
	private static final int						DEGREE					= 1;
	private static final IWeightFunction			WEIGHT					= new CubicSpline();
	private static final GaussQuadrature			GAUSS_QUADRATURE		= new GaussQuadrature(QuadraturePointsNumber);
	private static final double						LEFT_BOUNDERY			= -1;
	private static final double						RIGHT_BOUNDERY			= 1;

	private static final Map<Integer, ValuesHolder>	mapOfValues;

	// problem data
	protected EquationParameters					eParams;
	// element number starting from 0
	protected int									elNumber;
	// element information
	protected double								left;
	protected double								right;
	// results
	protected ElementMatrix							data;

	protected static List<ICalculate>				baseFunctions;
	protected List<Integer>							pos;

	static {

		// create MLS funcs;
		// 1 create particles;
		double hEFG = (RIGHT_BOUNDERY - LEFT_BOUNDERY) / (double) (NUMBER_OF_NODES);
		double rhoH = RHO * hEFG;
		// final double h = right - left;// fem h;
		final double h = hEFG;
		ArrayList<Particle> ls1D = new ArrayList<Particle>();
		for (int i = 0; i <= NUMBER_OF_NODES; i++) {
			ls1D.add(new Particle(LEFT_BOUNDERY + hEFG * i, rhoH));
		}
		// 2 create functions
		ArrayList<IFEM1D> fem = new ArrayList<IFEM1D>();
		fem.add(new FEMShapeFunction1D1(LEFT_BOUNDERY, h / 2.0));
		fem.add(new FEMShapeFunction1D1(RIGHT_BOUNDERY, h / 2.0));

		baseFunctions = new ArrayList<ICalculate>();

		// ModifiedFEMBaseFunction mf = new ModifiedFEMBaseFunction(DEGREE, ls1D.get(0), WEIGHT, fem, fem.get(0));
		// mf.setNodeList(ls1D);
		// baseFunctions.add(mf);
		baseFunctions.add((ICalculate) new FEMShapeFunction1D1(LEFT_BOUNDERY, (RIGHT_BOUNDERY - LEFT_BOUNDERY)));
		for (int i = 1; i < ls1D.size() - 1; i++) {
			// for (int i = 0; i < ls1D.size(); i++) {
			Particle particle = ls1D.get(i);
			BaseFunctionCoupled jmf1 = new BaseFunctionCoupled(DEGREE, particle, WEIGHT, fem);
			jmf1.setNodeList(ls1D);
			baseFunctions.add(jmf1);
		}
		// mf = new ModifiedFEMBaseFunctiion(DEGREE, ls1D.get(ls1D.size() - 1), WEIGHT, fem, fem.get(1));
		// mf.setNodeList(ls1D);
		// baseFunctions.add(mf);
		baseFunctions.add((ICalculate) new FEMShapeFunction1D1(RIGHT_BOUNDERY, (RIGHT_BOUNDERY - LEFT_BOUNDERY)));

		mapOfValues = new HashMap<Integer, ValuesHolder>();
		double[] xPoints = GAUSS_QUADRATURE.getX();
		for (int position = 0; position < baseFunctions.size(); position++) {
			ICalculate f = baseFunctions.get(position);
			double[] bounds = f.getBounds();
			bounds[0] = Math.max(bounds[0], LEFT_BOUNDERY);
			bounds[1] = Math.min(bounds[1], RIGHT_BOUNDERY);
			List<Values[]> valuesList = new ArrayList<Values[]>();
			ValuesHolder holder = new ValuesHolder(bounds[0], bounds[1], valuesList);
			for (int i = 0; i < baseFunctions.size(); i++) {
				ICalculate func = baseFunctions.get(i);
				Values[] values = new Values[xPoints.length];
				valuesList.add(values);
				for (int j = 0; j < xPoints.length; j++) {
					double realXValue = transformToOneOne(xPoints[j], bounds[0], bounds[1]);
					double[] res = func.calculateAll(realXValue);
					values[j] = new Values(res[0], res[1], realXValue);
				}
			}
			mapOfValues.put(position, holder);
		}
	}

	public PreCalculatedEnrichedLinearElement(EquationParameters eParams, int elNumber, double left, double right) {
		super();
		this.eParams = eParams;
		this.elNumber = elNumber;
		this.left = left;
		this.right = right;

		pos = new ArrayList<Integer>();
		for (int i = 0; i < baseFunctions.size(); i++) {
			pos.add(this.elNumber + i);
		}
	}

	@Override
	public double calculate(double x, double[] vals) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void generateAll() {
		double jackobianMatrix = 2.0 / (right - left);
		// generating values
		data = new ElementMatrix(pos);

		for (Integer position : mapOfValues.keySet()) {
			ValuesHolder holder = mapOfValues.get(position);
			double localLeft = holder.getLeft();
			double localRight = holder.getRight();
			List<Values[]> valuesList = holder.getValues();
			// for (int i = 0; i < data.vector.length; i++) {
			final Values[] phi1 = valuesList.get(position);
			double[] bounds = baseFunctions.get(position).getBounds();
			bounds[0] = Math.max(LEFT_BOUNDERY, localLeft);
			bounds[1] = Math.min(RIGHT_BOUNDERY, localRight);
			for (int j = 0; j < data.vector.length; j++) {
				final Values[] phi2 = valuesList.get(j);
				double res = 0.0;
				for (int index = 0; index < QuadraturePointsNumber; index++) {
					// double realXValue = phi1[index].x;
					double realXValue = transformToOneOne(phi1[index].x, left, right);
					// if (realXValue < left || realXValue > right) {
					// System.err.println("something bad happened: " + position + "\t" + j + "\t x =" + realXValue);
					// continue;
					// }
					double func = eParams.p.calculate(realXValue) * phi2[index].valDerivaty * phi1[index].valDerivaty * jackobianMatrix * jackobianMatrix
							+ eParams.w.calculate(realXValue) * phi2[index].valDerivaty * jackobianMatrix * phi1[index].val + eParams.q.calculate(realXValue) * phi2[index].val
							* phi1[index].val;
					// double func = phi2[index].val * phi1[index].val;
					res += GAUSS_QUADRATURE.getW(index) * func;
					// System.err.println(String.format("in integration - x = %s\tin function - x = %s\tphi1 = %s\tphi1' =%s\tin integration - res = %s",
					// Double.toString(realXValue),
					// Double.toString(realXValue), Double.toString(phi1[index].val), Double.toString(phi1[index].valDerivaty * jackobianMatrix), Double.toString(res)));
				}
				// System.err.println("before * res - " + res);
				res *= (bounds[1] - bounds[0]) / 2.0;
				// System.err.println("* integration multipl res - " + res);
				// apply Jacobian
				res *= (right - left) / 2.0;
				// System.err.println("final res - " + res);
				data.matrix[position][j] = (res);
				// System.err.println();
			}

			double res = 0.0;
			for (int index = 0; index < QuadraturePointsNumber; index++) {
				double realXValue = phi1[index].x;// transformToOneOne(transformFromOneOne(phi1[index].x, left, right), left, right);
				double func = eParams.f.calculate(realXValue) * phi1[index].val;
				res += GAUSS_QUADRATURE.getW(index) * func;
				// System.err.println("res = " + res);
			}
			// System.err.println();
			res *= (bounds[1] - bounds[0]) / 2.0;
			res *= (right - left) / 2.0;
			data.vector[position] = res;
			// }
		}

	}

	@Override
	public double[] generateFe() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public double[][] generatePe() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public double[][] generateQe() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public double[][] generateWe() {
		return null;
	}

	@Override
	public ElementMatrix getAll() {
		return data;
	}

	@Override
	public int lastNumber() {
		return this.elNumber + baseFunctions.size() - 1;
	}

	public static void main(String[] args) {

		XYSeriesCollection col = new XYSeriesCollection();

		for (Integer pos : mapOfValues.keySet()) {
			XYSeries s1 = new XYSeries("result1", true, true);
			Values[] values = PreCalculatedEnrichedLinearElement.mapOfValues.get(0).getValues().get(pos);
			for (int i = 0; i < values.length; i++) {
				s1.add(values[i].x, values[i].val);
			}
			col.addSeries(s1);
		}

		// NewOkCancelDialog d = new NewOkCancelDialog(null, false, col);
		// d.setLocationRelativeTo(null);
		// d.setVisible(true);
		EquationParameters eqP = new EquationParameters(new ConstantFunction1D(1.0), new ConstantFunction1D(0.0), new ConstantFunction1D(0.0), new ConstantFunction1D(1.0));
		PreCalculatedEnrichedLinearElement element = new PreCalculatedEnrichedLinearElement(eqP, 0, 0, 1);
		element.generateAll();
		MatrixLUStyle.printMatrix(element.getAll().matrix);
		MatrixLUStyle.printMatrix(element.getAll().vector);

		final int pos = 1;
		double[] bounds = baseFunctions.get(pos).getBounds();
		bounds[0] = Math.max(bounds[0], -1);
		bounds[1] = Math.min(bounds[1], 1);
		double left = bounds[0];
		double right = bounds[1];
		// System.err.println(left + "\t" + right);
		List<Values[]> valuesList = mapOfValues.get(pos).getValues();
		final Values[] phi1 = valuesList.get(pos);
		// double res = 0.0;
		// for (int index = 0; index < QuadraturePointsNumber; index++) {
		// // double realXValue = transformToOneOne(transformFromOneOne(phi1[index].x, left, right), left, right);
		// double func = phi1[index].valDerivaty;
		// System.err.println(phi1[index].x + "\t" + phi1[index].valDerivaty);
		// res += GAUSS_QUADRATURE.getW(index) * func;
		// }
		// res *= (right - left) / 2.0;
		// System.out.println(res);
		// System.out.println(GAUSS_QUADRATURE.integrate(left, right, new IFunction1D() {
		//
		// @Override
		// public double calculateDerivaty(double x) {
		// return 0;
		// }
		//
		// @Override
		// public double calculate(double x) {
		// return baseFunctions.get(pos).calculateDerivaty(x);
		// }
		// }));

	}

	private static double transformToOneOne(double value, double left, double right) {
		return (right - left) / 2.0 * value + (left + right) / 2.0;
	}

	private static double transformFromOneOne(double value, double left, double right) {
		return ((right + left) - 2 * value) / (left - right);
	}
}
